package its.opt.program;

import its.model.RID;
import its.model.environment.Environment;
import its.model.environment.crossroad.Crossroad;
import its.model.environment.crossroad.CrossroadWay;
import its.model.environment.crossroad.Program;
import its.model.environment.crossroad.ProgramPhase;
import its.model.environment.road.MainRoad;
import its.opt.OptSimConf;
import its.opt.StatsLoader;
import its.opt.data.RoadDataItem;
import its.opt.data.RoadDataManager;
import its.opt.utils.OptimizationEvaluatorUtils;
import its.sim.Simulator;
import its.sim.SimulatorConfiguration;
import its.sim.stats.StatDataType;
import its.util.TimeUtils;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

public class SimulationEvaluator implements Evaluator {

	private static final Logger LOGGER = Logger
			.getLogger(SimulationEvaluator.class.getName());

	private SimulatorConfiguration conf;

	private String[] args;

	public SimulationEvaluator(String[] args) {
		this.conf = new SimulatorConfiguration(args);
		this.args = args;
	}

	@Override
	public double eval(Map<RID, Program> programs) {

		File environmentFile =
			OptimizationEvaluatorUtils
				.storeModel(
						"testbuild" + "-" + System.getProperty("hostname"),
						"evotest",
						args,
						programs);

		RoadDataManager dataManager;
		try {
			dataManager = test(environmentFile);
		} catch (Exception e) {
			e.printStackTrace();
			LOGGER.log(Level.SEVERE, "Problem with simulation.", e);
			throw new IllegalStateException(e.getMessage());
		}

		return evaluate(programs, dataManager);
	}

	private double evaluate(Map<RID, Program> programs,
			RoadDataManager dataManager) {

		Set<RID> crossroads = programs.keySet();

		double result = 0.0;

		for (RID cr : crossroads) {
			result += evaluateProgram(programs.get(cr), dataManager);
		}
		// non zero result
		result += 1.0d;
		result *= getInOutVehiclesRatio(dataManager);
		return result;
	}

	private double evaluateProgram(Program program, RoadDataManager dataManager) {

		RID crId = program.getCrossroadID();
		Environment activeEnvironment = Environment.getActiveEnvironment();
		Crossroad cr = activeEnvironment.getCrossroad(crId);
		Set<RID> inwayIDs = cr.getInwayIDs();

		double result = 0.0;
		Map<ProgramPhase, Long> phaseDelayMap = new HashMap<ProgramPhase, Long>();
		for (RID inId : inwayIDs) {
			 RoadDataItem inData = dataManager.getRoadDataFor(inId);
			// long delayRoadTotal = inData.getDelayTotal();
			 Set<RID> outIds = inData.getForwardReferences();
			MainRoad inRoad = activeEnvironment.getMainRoad(inId);
			// long totalQDelay = 0L;
			for (RID outId : outIds) {
				MainRoad target = activeEnvironment.getMainRoad(outId);
				CrossroadWay way = cr.getWay(inRoad, target);
				RID qId = way.getQueueID();
				// RoadDataItem qData = dataManager.getRoadDataFor(qId);
				// totalQDelay += qData.getDelayTotal();
				String sigGroup = program.getSignalizationGroupIdForLaneId(way
						.getLaneID());
				Set<ProgramPhase> ps = program.getPhasesForSignalGroup(sigGroup);
				// long durSum = 0L;
				for (ProgramPhase phase : ps) {
					// durSum += phase.getDuration();
					RoadDataItem qData = dataManager.getRoadDataFor(qId);
					if (!phaseDelayMap.containsKey(phase)) {
						phaseDelayMap.put(phase, qData.getDelayTotal());
					} else {
						Long delay = phaseDelayMap.get(phase) + qData.getDelayTotal();
						phaseDelayMap.put(phase, delay);
					}
				}
			}
		}

		long delayTotal = 0L;
		for (Long delay : phaseDelayMap.values()) {
			delayTotal += delay;
		}

		long maxPhaseDur = 
			TimeUtils.convertSecondsToMillis(
					ProgramBuilder.MAX_PHASE_LENGTH - ProgramBuilder.MIN_PHASE_LENGTH);
		for (Map.Entry<ProgramPhase, Long> e : phaseDelayMap.entrySet()) {
			ProgramPhase p = e.getKey();
			Long stripedPhaseDur = p.getDuration() - TimeUtils.convertSecondsToMillis(ProgramBuilder.MIN_PHASE_LENGTH);
			double phaseRatio = (double) stripedPhaseDur  / (double) maxPhaseDur;
			Long phaseDelay = e.getValue();
			double delayRatio = (delayTotal == 0L) ? 0.0d : ((double) phaseDelay / (double) delayTotal);
			
			result += Math.abs(phaseRatio - delayRatio);
		}

		// double actPhaseQDelay = 0.0;
		// Iterator<ProgramPhase> pIter = program.getPhaseIterator();
		// while (pIter.hasNext()) {
		// ProgramPhase phase = (ProgramPhase) pIter.next();
		// actPhaseQDelay = sumQsDelayForPhase(phase, cr, dataManager);
		// }
		return result;
	}

	private RoadDataManager test(File config) throws Exception {
		SimulatorConfiguration simconf = new OptSimConf(conf, config);
		Simulator sim = new Simulator(simconf);
		LOGGER.info("Running simulation from config " + config.getPath());
		sim.run();
		StatsLoader loader = new StatsLoader(sim);
		RoadDataManager manager = new RoadDataManager();

		manager.addConnections(loader.getDataSet(StatDataType.ROUTE));
		manager.addDelayTimes(loader.getDataSet(StatDataType.ROAD_DELAY));
		manager.addStopTimes(loader.getDataSet(StatDataType.ROAD_STOP_TIMES));
		manager.addPassTimes(loader.getDataSet(StatDataType.ROAD_PASS_TIMES));
		manager.addStopCount(loader.getDataSet(StatDataType.ROAD_STOPS));
		manager.addEnteredCount(loader.getDataSet(StatDataType.ROAD_ENTERING));
		return manager;
	}

	private double getInOutVehiclesRatio(RoadDataManager test) {
		return ((double) test.getEnteredCount()) / ((double) test.getCount() + 1.0d);
	}
}
